/*-------------------------------------------------------------------------
 *
 * deparse_sequence_stmts.c
 *
 *	  All routines to deparse sequence statements.
 *	  This file contains all entry points specific for sequence statement
 *    deparsing
 *
 * Copyright (c), Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/namespace.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"

#include "distributed/deparser.h"
#include "distributed/version_compat.h"

/* forward declaration for deparse functions */
static void AppendDropSequenceStmt(StringInfo buf, DropStmt* stmt);
static void AppendSequenceNameList(StringInfo buf, List* objects, ObjectType objtype);
static void AppendRenameSequenceStmt(StringInfo buf, RenameStmt* stmt);
static void AppendAlterSequenceSchemaStmt(StringInfo buf, AlterObjectSchemaStmt* stmt);
static void AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt* stmt);
static void AppendAlterSequencePersistenceStmt(StringInfo buf, AlterTableStmt* stmt);
static void AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt* stmt);
static void AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt* stmt);

/*
 * DeparseDropSequenceStmt builds and returns a string representing the DropStmt
 */
char* DeparseDropSequenceStmt(Node* node)
{
    DropStmt* stmt = castNode(DropStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->removeType == OBJECT_SEQUENCE);

    AppendDropSequenceStmt(&str, stmt);

    return str.data;
}

/*
 * AppendDropSequenceStmt appends a string representing the DropStmt to a buffer
 */
static void AppendDropSequenceStmt(StringInfo buf, DropStmt* stmt)
{
    appendStringInfoString(buf, "DROP SEQUENCE ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    AppendSequenceNameList(buf, stmt->objects, stmt->removeType);

    if (stmt->behavior == DROP_CASCADE) {
        appendStringInfoString(buf, " CASCADE");
    }

    appendStringInfoString(buf, ";");
}

/*
 * AppendSequenceNameList appends a string representing the list of sequence names to a
 * buffer
 */
static void AppendSequenceNameList(StringInfo buf, List* objects, ObjectType objtype)
{
    ListCell* objectCell = NULL;
    foreach (objectCell, objects) {
        if (objectCell != list_head(objects)) {
            appendStringInfo(buf, ", ");
        }

        RangeVar* seq = makeRangeVarFromNameList((List*)lfirst(objectCell));

        char* qualifiedSequenceName =
            quote_qualified_identifier(seq->schemaname, seq->relname);
        appendStringInfoString(buf, qualifiedSequenceName);
    }
}

/*
 * DeparseRenameSequenceStmt builds and returns a string representing the RenameStmt
 */
char* DeparseRenameSequenceStmt(Node* node)
{
    RenameStmt* stmt = castNode(RenameStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->renameType == OBJECT_SEQUENCE);

    AppendRenameSequenceStmt(&str, stmt);

    return str.data;
}

/*
 * AppendRenameSequenceStmt appends a string representing the RenameStmt to a buffer
 */
static void AppendRenameSequenceStmt(StringInfo buf, RenameStmt* stmt)
{
    RangeVar* seq = stmt->relation;

    char* qualifiedSequenceName =
        quote_qualified_identifier(seq->schemaname, seq->relname);

    appendStringInfoString(buf, "ALTER SEQUENCE ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    appendStringInfoString(buf, qualifiedSequenceName);

    appendStringInfo(buf, " RENAME TO %s", quote_identifier(stmt->newname));
}

/*
 * DeparseAlterSequenceSchemaStmt builds and returns a string representing the
 * AlterObjectSchemaStmt
 */
char* DeparseAlterSequenceSchemaStmt(Node* node)
{
    AlterObjectSchemaStmt* stmt = castNode(AlterObjectSchemaStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->objectType == OBJECT_SEQUENCE);

    AppendAlterSequenceSchemaStmt(&str, stmt);

    return str.data;
}

/*
 * AppendAlterSequenceSchemaStmt appends a string representing the AlterObjectSchemaStmt
 * to a buffer
 */
static void AppendAlterSequenceSchemaStmt(StringInfo buf, AlterObjectSchemaStmt* stmt)
{
    RangeVar* seq = stmt->relation;

    char* qualifiedSequenceName =
        quote_qualified_identifier(seq->schemaname, seq->relname);

    appendStringInfoString(buf, "ALTER SEQUENCE ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    appendStringInfoString(buf, qualifiedSequenceName);

    appendStringInfo(buf, " SET SCHEMA %s;", quote_identifier(stmt->newschema));
}

/*
 * DeparseAlterSequenceOwnerStmt builds and returns a string representing the
 * AlterTableStmt consisting of changing the owner of a sequence
 */
char* DeparseAlterSequenceOwnerStmt(Node* node)
{
    AlterTableStmt* stmt = castNode(AlterTableStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->relkind == OBJECT_SEQUENCE);

    AppendAlterSequenceOwnerStmt(&str, stmt);

    return str.data;
}

/*
 * AppendAlterSequenceOwnerStmt appends a string representing the AlterTableStmt to a
 * buffer consisting of changing the owner of a sequence
 */
static void AppendAlterSequenceOwnerStmt(StringInfo buf, AlterTableStmt* stmt)
{
    Assert(stmt->relkind == OBJECT_SEQUENCE);
    RangeVar* seq = stmt->relation;
    char* qualifiedSequenceName =
        quote_qualified_identifier(seq->schemaname, seq->relname);
    appendStringInfoString(buf, "ALTER SEQUENCE ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    appendStringInfoString(buf, qualifiedSequenceName);

    ListCell* cmdCell = NULL;
    foreach (cmdCell, stmt->cmds) {
        if (cmdCell != list_head(stmt->cmds)) {
            /*
             * normally we shouldn't ever reach this
             * because we enter this function after making sure we have only
             * one subcommand of the type AT_ChangeOwner
             */
            ereport(ERROR, (errmsg("More than one subcommand is not supported "
                                   "for ALTER SEQUENCE")));
        }

        AlterTableCmd* alterTableCmd = castNode(AlterTableCmd, lfirst(cmdCell));
        switch (alterTableCmd->subtype) {
            case AT_ChangeOwner: {
                appendStringInfo(buf, " OWNER TO %s;", alterTableCmd->name);
                break;
            }

            default: {
                /*
                 * normally we shouldn't ever reach this
                 * because we enter this function after making sure this stmt is of the
                 * form ALTER SEQUENCE .. OWNER TO ..
                 */
                ereport(ERROR,
                        (errmsg("unsupported subtype for alter sequence command"),
                         errdetail("sub command type: %d", alterTableCmd->subtype)));
            }
        }
    }
}

/*
 * DeparseAlterSequencePersistenceStmt builds and returns a string representing
 * the AlterTableStmt consisting of changing the persistence of a sequence
 */
char* DeparseAlterSequencePersistenceStmt(Node* node)
{
    AlterTableStmt* stmt = castNode(AlterTableStmt, node);
    StringInfoData str = {0};
    initStringInfo(&str);

    Assert(stmt->relkind == OBJECT_SEQUENCE);

    AppendAlterSequencePersistenceStmt(&str, stmt);

    return str.data;
}

/*
 * AppendAlterSequencePersistenceStmt appends a string representing the
 * AlterTableStmt to a buffer consisting of changing the persistence of a sequence
 */
static void AppendAlterSequencePersistenceStmt(StringInfo buf, AlterTableStmt* stmt)
{
    Assert(stmt->relkind == OBJECT_SEQUENCE);

    RangeVar* seq = stmt->relation;
    char* qualifiedSequenceName =
        quote_qualified_identifier(seq->schemaname, seq->relname);
    appendStringInfoString(buf, "ALTER SEQUENCE ");

    if (stmt->missing_ok) {
        appendStringInfoString(buf, "IF EXISTS ");
    }

    appendStringInfoString(buf, qualifiedSequenceName);

    ListCell* cmdCell = NULL;
    foreach (cmdCell, stmt->cmds) {
        if (cmdCell != list_head(stmt->cmds)) {
            /*
             * As of PG15, we cannot reach this code because ALTER SEQUENCE
             * is only supported for a single sequence. Still, let's be
             * defensive for future PG changes
             */
            ereport(ERROR, (errmsg("More than one subcommand is not supported "
                                   "for ALTER SEQUENCE")));
        }

        AlterTableCmd* alterTableCmd = castNode(AlterTableCmd, lfirst(cmdCell));
        switch (alterTableCmd->subtype) {
#ifdef DISABLE_OG_COMMENTS
            case AT_SetLogged: {
                appendStringInfoString(buf, " SET LOGGED;");
                break;
            }

            case AT_SetUnLogged: {
                appendStringInfoString(buf, " SET UNLOGGED;");
                break;
            }
#endif
            default: {
                /*
                 * normally we shouldn't ever reach this
                 * because we enter this function after making sure this stmt is of the
                 * form ALTER SEQUENCE .. SET LOGGED/UNLOGGED
                 */
                ereport(ERROR,
                        (errmsg("unsupported subtype for alter sequence command"),
                         errdetail("sub command type: %d", alterTableCmd->subtype)));
            }
        }
    }
}

/*
 * DeparseGrantOnSequenceStmt builds and returns a string representing the
 * GrantOnSequenceStmt
 */
char* DeparseGrantOnSequenceStmt(Node* node)
{
    GrantStmt* stmt = castNode(GrantStmt, node);
    Assert(stmt->objtype == ACL_OBJECT_SEQUENCE);

    StringInfoData str = {0};
    initStringInfo(&str);

    AppendGrantOnSequenceStmt(&str, stmt);

    return str.data;
}

/*
 * AppendGrantOnSequenceStmt builds and returns an SQL command representing a
 * GRANT .. ON SEQUENCE command from given GrantStmt object.
 */
static void AppendGrantOnSequenceStmt(StringInfo buf, GrantStmt* stmt)
{
    Assert(stmt->objtype == ACL_OBJECT_SEQUENCE);

    if (stmt->targtype == ACL_TARGET_ALL_IN_SCHEMA) {
        /*
         * Normally we shouldn't reach this
         * We deparse a GrantStmt with OBJECT_SEQUENCE after setting targtype
         * to ACL_TARGET_OBJECT
         */
        elog(ERROR, "GRANT .. ALL SEQUENCES IN SCHEMA is not supported for formatting.");
    }

    AppendGrantSharedPrefix(buf, stmt);

    AppendGrantOnSequenceSequences(buf, stmt);

    AppendGrantSharedSuffix(buf, stmt);
}

/*
 * AppendGrantOnSequenceSequences appends the sequence names along with their arguments
 * to the given StringInfo from the given GrantStmt
 */
static void AppendGrantOnSequenceSequences(StringInfo buf, GrantStmt* stmt)
{
    Assert(stmt->objtype == ACL_OBJECT_SEQUENCE);

    appendStringInfoString(buf, " ON SEQUENCE ");

    ListCell* cell = NULL;
    foreach (cell, stmt->objects) {
        /*
         * GrantOnSequence statement keeps its objects (sequences) as
         * a list of RangeVar-s
         */
        RangeVar* sequence = (RangeVar*)lfirst(cell);

        /*
         * We have qualified the statement beforehand
         */
        appendStringInfoString(
            buf, quote_qualified_identifier(sequence->schemaname, sequence->relname));

        if (cell != list_tail(stmt->objects)) {
            appendStringInfoString(buf, ", ");
        }
    }
}
